WPF Expander Control – Cool Things Part 2 (Animation and Auto-collapsing)

Hmm…looking back at how I did my first “tutorial” on basic animation with the expander control, I realize that I did a very poor job of displaying how to animate — as well as making a scalable/reusable solution. Well, today, I’m going to fix that. Today, we’re going to make a custom expander control the right way. We’re going to use Expression Blend when we can, and we’re going to inherit our control from the Expander control. Not only that, we’re going to give ourselves the option of having animation…or not having animation — and we’re going to give ourselves some measure of control over how our animation takes place. So let’s get started.

The first thing we need to do, is fire up our copy of Microsoft Expression Blend (at the time of this writing…I’m using Beta 2). We want to create a new Control Library…so I recommend you select that as your project type, and name it something like CustomControls. Ideally, you’ll probably want to reuse your custom Expander control, and you’ll probably want to add more controls to this library. So once that’s done, open up the XAML view of your first control, and you should see something like this:

Xaml view of UserControl1

Now, in a previous tutorial, I made it a point to say that you can inherit your custom User Control from ANY control. By default, you inherit from UserControl, but in this case we want to inherit from the Expander Control. Why? – because we just want to customize the current Expander Control…only adding minimal improvements. So what you need to do is replace the UserControl markup with Expander. Be sure to remove the Grid Control from the XAML, because you’ll want to leave the Content of your control empty. I suggest that you also rename your XAML and code-behind files/classes to the name that you intend to give the Control. I named my control CustomExpander, and my partial class to CustomExpanderControl (remember that your class and control’s names have to be different). Another thing that I’m going to make specific to my control, is its initial size and ExpandDirection. I want my control to expand to the right. So after making the necessary changes, my XAML looks like this:

Xaml of the CustomExpander

Now that we’ve finished with that, you need to add some static animations and triggers to the control. The reason why we want to do this is because it’s easier to manipulate something that already exists, rather than instantiating it every time we need use it. So, the first thing we need to do is add some triggers to our control to handle the Expander.Collapsed and Expander.Expanded events. So switch to your Design View, and add some triggers by click on the “Add Event Trigger” button.

Add event trigger button

After you’ve added triggers for both of these events, let’s go ahead and edit our animation timelines. Go ahead and create keyframes for what you want your custom Expander Control to expand/collapse to. You need to create a keyframe for the beginning of each animation, as well as the end. For the sake of this tutorial, these values need to be reflections of each other (i.e. if you want to Collapse the width from 150 to 25, then you need to also make the control expand from 25 to 150). This is very important, because the way we’ll handle editing the animation properties depends on it. For this tutorial, I’ve decided to expand the control from a width of 25 (collapsed) to 150 (expanded), and the opacity to go from 25 to 100. I’ve also made my control take 1/2 second to animate. It doesn’t matter if you use the same values, just as long as they’re consistent.

Animation Timelines

So after you’ve edit your control’s properties, added triggers, and create some animation timelines — we’ve done the “hard part”, and it’s time to edit the code-behind file in order to expose some properties that will make our control easier to use and customize. So go ahead and close out of Expression Blend (we’re done with it), and open your project in Visual Studio. Now, I mentioned auto-collapse as a feature that we’ll want to use in our control, as well as the ability to enable/disable animation. So the first thing we need to do is add some private member variables to our controls.

Initial class members

The TriggerCollection is for saving our control’s triggers — I’ll explain why later. We’re going to use the timer for timing how long before our control auto-collapses and the staysOpen member will control whether or not the control auto-collapses at all. This should give you an idea of “how” we’re going to do what we need to do. But there’s still some more customization features we can/will add. First of all we need some way to edit the animation timelines — so let’s expose some private properties that will help us do that.

Storyboard members

We expose these two properties, because they give us direct access to our two different animation storyboards. The properties below control the width of our collapsed/expanded animations.  They reflect the values that were stored in the static keyframes we created earlier. This is the reason why created a start frame, and an end frame in our timeline. These are all the private properties we need to expose some public properties.

Expander width properties

Now, let’s list our public properties. It should be fairly obvious how these properties can/will be used. The Width properties control how big the control will be when expanded/collapsed — and the Opacity properties specify the control’s opacity during these two states. By editing these programmatically, one can control the animation of our control with a relative amount of ease.

Public properties

Now it’s time to add the “mack-daddy” of properties…and explain why we have a TriggerCollection.

AnimationEnabled property

This property will control whether our control animates or not. The way we want to control this is by adding/removing triggers from our control. Rather than adding/removing the actual storyboards, or animation timelines — we can just remove the events that call them. This makes our job alot easier. So first we’ll save our triggers in a TriggerCollection at the time our control is instantiated. Then when animation is disabled in the control, we’ll remove all the triggers. When animation is re-enabled we just add back the triggers we already saved.

Those are all the properties we need to declare, but we still need to enable the auto-collapsing function, as well as save the triggers. To enable the auto-collapsing function, we want the control to start “timing” on a MouseLeave event, and stop/reset on a MouseEnter event. This brings up something very important to developers new to .NET Framwork 3.0. Since we’re going to use a Timer to kick-off our auto-collapse, the Timer is going to need a way to invoke our Collapse event from a different thread. In .NET 2.0, all controls had an Invoke method they inherited for these kinds of cross-thread actions. But .NET 3.0 has taken that out of the control scope. Instead, to access objects in the UI thread, you have to used the Dispather object. (Go here for more information: http://msdn2.microsoft.com/en-us/library/ms591593.aspx)

So let’s go ahead and instantiate the objects that haven’t already been instantiated…and go ahead and create our various event handlers. This is done in our control’s constructor.

Expander Contructor

Now that we’ve done that, the first thing we need to do is create a delegate for making this auto-collapse call. So we need to add a delegate type like this one to our class:

ExpanderDelegate

Simple enough? Now, let’s create a function that will set the IsExpanded property to false, as well as call this function (using a delegate) from our Timer’s Elapsed event handler. Take a special note of the way you use the Dispatcher.Invoke method.

ExpanderDelegate implementation

All that’s left for us to do now, is to simply handle the MouseEnter/Leave events. Basically, when the mouse leave the control, if animation is enabled, and StaysOpen is false, then we’ll start the Timer. When the mouse enters the control, we’ll stop the timer. If the Timer is already stopped, then it won’t throw an error, or affect the controls state at all.

Expander MouseEnter/Leave

Now we are done. Just compile your project, and start using this cool custom Expander. By default, the control will animate with whatever animation properties you’ve assigned it. But these properties can all be accessed/changed programmatically throught the code-behind find or through XAML. By exposing these properties, they can also be edited in Expression Blend, under the Miscellaneous tab. Now isn’t that great?

Custom Expander in Expression Blend

5 Responses to WPF Expander Control – Cool Things Part 2 (Animation and Auto-collapsing)

  1. needbrew says:

    Do you have the source code for this? I have tried to follow along and cannot get my control to work in Blend at all. Not sure what I am missing

    Cool tutorial though.

  2. needbrew:

    I don’t believe that I have the original code laying around, but if I get some time today I’ll redo this and post the code somewhere.

  3. Karl Shifflett says:

    Nice post. Are you going to keep blogging? I’ve like reading your posts.

    Cheers,

    Karl

  4. kovacp1 says:

    Did you ever post the code anywhere as discused in the thread?
    Thank you!

  5. mayhejr says:

    Hi does anybody knows how to create a custom expander to show a panel also with animation?

Leave a comment